import base64
import datetime
import hashlib
import json
import os
from enum import Enum
from typing import Dict, List, Optional, Iterator
from urllib.parse import urlparse
import arrow
from flask import request  # type: ignore
from bson import ObjectId, Binary  # type: ignore

from app.db import Pagination
from app.libs.utils import (
    singleton,
    utc_now,
    datetime_from_ts,
)
from app.libs.enums import (
    InspectionEventMarkerStatus,
    InspectionStrategySwitchStatus,
    InspectionStrategyIgnoreType,
)
from app.db.models.asset_tasks import AssetTasks
from app.db.models.wass import ScanEvents
from app.db.models.inspection import (
    IgnoreStrategies,
    ScanEventsInspection,
    SupportingImages,
)
from app.db.models.alert_groups import AlertGroup
from app.db.models.users import User
from app.db.models.tasks import Task

from app.log.logs import (
    CSecurityInspectionLog,
    USecurityInspectionLog,
    DSecurityInspectionLog,
)


FRONT_SCAN_EVENT_ARGS_MAPPING = {
    "eventName": "event_name",
    "eventId": "event_id",
    "eventType": "event_type",
    "taskSessionId": "task_session_id",
    "key": "detail.key",
    "engine": "job_name",
    "url": "target_url",
    "severity": "severity",
    "start": "start",
    "end": "end",
    "page": "page",
    "perPage": "perPage",
}


TASK_EVENT_TYPE_NAME_MAPPING = {
    "malscan": "挂马",
    "black_links": "暗链",
    "cryjack": "挖矿",
    "broken_links": "坏链",
    "foreign_links": "风险外链",
}


SCAN_EVENT_TYPE_NAME_MAPPING = {
    "malscan": "挂马",
    "black_links": "暗链",
    "cryptojacking": "挖矿",
    "broken_links": "坏链",
    "risk_link": "风险外链",
}


FRONT_ENGINE_KEY_MAPPING = {
    "risk_link": "foreign_links",
    "cryptojacking": "cryjack",
    "statistics": "broken_links"
}


RISK_TYPE = {
    1: {
        "en_us": "icp",
        "zh_cn": "未备案",
    },
    2: {"en_us": "keyword", "zh_cn": "内容违规"},
    3: {"en_us": "domain", "zh_cn": "风险域名"},
    4: {"en_us": "cryjack", "zh_cn": "seo hijack"},
}


ENGINE_PLUGINS_CODE = {
    "nscan": {
        "path": "https://gitlab.intra.knownsec.com/websoc/scanner/nscan/-/tree/develop/plugins",
        "ext": ".py",
    },
    "nuclei": {
        "path": "https://gitlab.intra.knownsec.com/prd/nuclei-plugins/-/tree/main/nuclei-templates",
        "ext": ".yaml",
    },
    "risk_link": {
        "path": "https://gitlab.intra.knownsec.com/websoc/scanner/nscan/-/tree/develop/plugins/risk_link.py",
    },
    "black_links": {
        "path": "https://gitlab.intra.knownsec.com/websoc/scanner/nscan/-/tree/develop/plugins/black_links.py",
    },
    "cryjack": {
        "path": "https://gitlab.intra.knownsec.com/websoc/scanner/appscan/-/blob/develop/appscan/plugins/cryjack/entry.py"
    },
    "malscan": {
        "path": "https://gitlab.intra.knownsec.com/websoc/scanner/appscan/-/blob/develop/appscan/plugins/malscan/entry.py"
    },
}


NUMBER_ENGINE_MAPPING = {
    1: "appscan",
    2: "ascan",
    3: "kscan",
    4: "network_scan",
    5: "nscan",
    6: "site_info",
    7: "nuclei",
}


MARKER_NAME_MAPPING = {
    0: "未标记",
    1: "忽略",
    2: "误报",
    3: "确认",
}


LOG_SOURCE = "back"


class QueryType(Enum):
    FIND = "find"
    AGG = "aggregate"
    CREATE = "create"
    UPDATE = "update"
    DELETE = "delete"


class MSG(Enum):
    OK = "ok"
    ERROR = "error"
    NOT_FOUND = "not found"
    NOT_AVAILABLE = "not available"


class Code(Enum):
    OK = 200
    NOT_FOUND = 404
    NOT_AVAILABLE = 403


EVENT_NAME_LIST = [
    "vuln",
    "malscan",
    "black_links",
    "cryptojacking",
    "broken_links",
    "risk_link",
    "statistics"
]

EVENT_NAME_MAPPING = {
    "漏洞": "vuln",
    "挂马": "malscan",
    "暗链": "black_links",
    "挖矿": "cryptojacking",
    "坏链": "broken_links",
    "风险外链": "risk_link",
}

TASK_TYPES = ["vul", "securityEvent"]


def _split_int_content_string(value) -> List[int]:
    return [int(v.strip()) for v in value.split(",") if v.strip()]


class FailedResponse(object):
    NOT_FOUND = {
        "code": Code.OK.value,
        "msg": MSG.NOT_FOUND.value,
        "results": {},
    }

    NOT_AVAILABLE = {
        "code": Code.OK.value,
        "msg": MSG.NOT_AVAILABLE.value,
        "results": {},
    }


class _Query(object):
    def __init__(
        self,
        query_type: Enum,
        queries: Dict or List,
        selected_fields: Optional[List] = [],
        pagination: Optional[tuple] = None,
        order_by: Optional[str] = "",
    ):
        self.query_type = query_type
        self.queries = queries
        self.pagination = pagination
        self.selected_fields = selected_fields
        self.order_by = order_by


class _Result(object):
    def __init__(self, results, result_type: str, count=0):
        self._count: int = count if count else len(results)
        self._results: List = results
        self._type = result_type

    @property
    def count(self):
        return self._count

    @property
    def results(self):
        return self._results

    @property
    def type(self):
        return self._type

    def __iter__(self):
        for r in self._results:
            yield r

    @classmethod
    def from_find_res(cls, res):
        if isinstance(res, Pagination):
            results = res.items
        else:
            results = res
        if not results:
            results = []
        ins = cls(results, QueryType.FIND.value)
        return ins

    @classmethod
    def from_agg_res(cls, res):
        ins = cls(res, QueryType.AGG.value, count=1)
        return ins

    @classmethod
    def from_create_res(cls, res):
        ins = cls(res, QueryType.CREATE.value)
        return ins

    @classmethod
    def from_update_res(cls, res):
        if res == 0:
            ins = cls([], QueryType.UPDATE.value)
        else:
            ins = cls([res], QueryType.UPDATE.value)
        return ins

    @classmethod
    def from_delete_res(cls, res):
        if res == 0:
            ins = cls([], QueryType.DELETE.value)
        else:
            ins = cls([res], QueryType.DELETE.value)
        return ins

    @classmethod
    def from_none(cls):
        ins = cls([], "")
        return ins


class BaseHandler(object):
    def __init__(self, **kwargs):
        self.form = kwargs.get("form")
        self.form_data = getattr(self.form, "data", {})
        self._request = request
        self.col = None

    def execute(self, query: _Query = None, col=None) -> _Result:
        query = query
        col = col if col else self.col
        res = getattr(self, f"_{query.query_type.value}")(query, col)
        return res

    def _find(self, query: _Query, col) -> _Result:
        if query.pagination:
            if query.order_by:
                res = (
                    col.objects.only(*query.selected_fields)
                    .find(query.queries)
                    .order_by(query.order_by)
                    .skip((query.pagination[0] - 1) * query.pagination[1])
                    .limit(query.pagination[1])
                )
            else:
                res = (
                    col.objects.only(*query.selected_fields)
                    .find(query.queries)
                    .skip((query.pagination[0] - 1) * query.pagination[1])
                    .limit(query.pagination[1])
                )
        else:
            if query.order_by:
                res = (
                    col.objects.only(*query.selected_fields)
                    .find(query.queries)
                    .order_by(query.order_by)
                )
            else:
                res = col.objects.only(*query.selected_fields).find(
                    query.queries
                )
        result = _Result.from_find_res(res)
        return result

    def _aggregate(self, query: _Query, col) -> _Result:
        pipeline: List = query.queries
        if query.pagination:
            pipeline.extend(
                [
                    {"$limit": query.pagination[0] * query.pagination[1]},
                    {"$skip": (query.pagination[0] - 1) * query.pagination[1]},
                ]
            )
        if query.selected_fields:
            pipeline.append(
                {"$project": {field: 1 for field in query.selected_fields}}
            )
        res = col.objects.aggregate(pipeline)
        result = _Result.from_agg_res(res)
        return result

    def _create(self, query, col) -> _Result:
        if not isinstance(query.queries, list):
            query.queries = [query.queries]
        inserts = [col.from_dict(_query) for _query in query.queries]
        res = col.objects.insert(inserts)
        result = _Result.from_create_res(res)
        return result

    def _update(self, query, col) -> _Result:
        filters = query.queries.get("filters", {})
        upsert = query.queries.get("upsert", False)
        if not filters:
            return _Result.from_none()
        update = query.queries.get("update", {})
        res = col.objects.filter(**filters).update(upsert=upsert, **update)
        result = _Result.from_update_res(res)
        return result

    def _delete(self, query, col) -> _Result:
        filters = query.queries.get("filters", {})
        if not filters:
            return _Result.from_none()
        res = col.objects.filter(**filters).delete()
        result = _Result.from_delete_res(res)
        return result

    def get_user_with_task_sessions(self, task_session_id_list: List) -> Dict:
        task_session_user_mapping = {}
        if not task_session_id_list:
            return task_session_user_mapping
        pipeline = [
            {
                "$lookup": {
                    "from": "users",
                    "localField": "uid",
                    "foreignField": "_id",
                    "as": "result",
                }
            },
            {"$match": {"taskSessionId": {"$in": task_session_id_list}}},
        ]
        query = _Query(
            query_type=QueryType.AGG,
            queries=pipeline,
            selected_fields=["taskSessionId", "uid", "result.username"],
        )
        res = self.execute(query, col=Task)
        for r in res.results:
            if not r.get("taskSessionId"):
                continue
            if not r.get("result"):
                continue
            if not r["result"][0].get("username"):
                continue
            task_session_user_mapping[r["taskSessionId"]] = r["result"][0].get(
                "username"
            )
        return task_session_user_mapping


@singleton
class _IgnoreStrategiesHandler(BaseHandler):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.col = IgnoreStrategies

    def _select_fields(self) -> List:
        return IgnoreStrategies._fields.keys()

    def _init_query(self, **kwargs) -> _Query:
        ignore_type = kwargs.get("ignoreType")
        queries = {}
        if ignore_type:
            queries["ignoreType"] = ignore_type
        queries["switchStatus"] = InspectionStrategySwitchStatus.OPEN.value
        query = _Query(
            query_type=QueryType.FIND,
            queries=queries,
            selected_fields=self._select_fields(),
        )
        return query

    def _init_strategies(self, ignore_type=None) -> Dict:
        query = self._init_query(**{"ignoreType": ignore_type})
        strategies = self.execute(query)
        _all_strategies = {}
        for strategy in strategies.results:
            _id = strategy.id
            key = strategy.key
            url = strategy.url
            if not _all_strategies.get(key):
                _all_strategies[key] = {url: _id}
            _all_strategies[key][url] = _id
        return _all_strategies

    @property
    def all_strategies(self) -> Dict:
        return self._init_strategies()

    @property
    def ignore_strategies(self) -> Dict:
        return self._init_strategies(
            ignore_type=InspectionStrategyIgnoreType.IGNORED.value
        )

    @property
    def false_positive_strategies(self) -> Dict:
        return self._init_strategies(
            ignore_type=InspectionStrategyIgnoreType.FALSE_POSITIVE.value
        )


class EventListHandler(BaseHandler):
    def __init__(self, **kwargs):
        self.ignore_strategies_handler = _IgnoreStrategiesHandler()
        super().__init__(**kwargs)
        self.col = ScanEvents

    def _get_results(self, **kwargs):
        results = self._load_events_pipeline(kwargs)
        return results

    def _load_events_pipeline(self, params):
        marker_status = []
        queries = {}
        if params["markerStatus"]:
            marker_status = _split_int_content_string(params["markerStatus"])

        today = arrow.get(utc_now().date()).datetime
        start = today - datetime.timedelta(days=1)
        if params["start"]:
            start = datetime_from_ts(params["start"])
        end = today
        if params["end"]:
            end = datetime_from_ts(params["end"])

        queries["created_at"] = {
            "$gte": start,
            "$lt": end,
        }
        # 风险地址搜索暂时只对detail.affect生效
        if affect := params.get("riskUrl"):
            queries["detail.affect"] = {"$regex": str(affect)}

        if params["severity"]:
            severities = _split_int_content_string(params["severity"])
            # 忽略info级别数据
            if 1 in severities:
                severities.remove(1)
            queries["severity"] = {"$in": severities}

        if params["engine"]:
            queries["job_name"] = {
                "$in": [
                    NUMBER_ENGINE_MAPPING.get(v)
                    for v in _split_int_content_string(params["engine"])
                ]
            }

        if params["eventName"]:
            if params["eventName"] in EVENT_NAME_MAPPING:
                if params["eventType"] and params["eventType"] != 2:
                    params["eventType"] = 2
                queries["event_name"] = EVENT_NAME_MAPPING[params["eventName"]]
            else:
                if params["eventType"] and params["eventType"] != 1:
                    params["eventType"] = 1
                queries["detail.name.zh_cn"] = {"$regex": params["eventName"]}
        else:
            queries["event_name"] = {"$in": EVENT_NAME_LIST}

        if params["url"]:
            queries["target_url"] = params["url"]

        if params["taskSessionId"]:
            queries["task_session_id"] = params["taskSessionId"]

        if params["eventType"] and not params["eventName"]:
            if params["eventType"] == 1:
                queries["event_name"] = EVENT_NAME_LIST[0]
            else:
                queries["event_name"] = {"$in": EVENT_NAME_LIST[1:]}

        if params["key"]:
            queries["detail.key"] = {
                "$regex": params["key"],
            }

        # 加载指定用户的事件
        if params["username"]:
            task_session_id_list = self._get_task_sessions_with_uid(
                params, queries["created_at"]
            )
            if task_session_id_list:
                if params["taskSessionId"]:
                    if params["taskSessionId"] not in task_session_id_list:
                        return FailedResponse.NOT_FOUND
                    else:
                        queries["task_session_id"] = params["taskSessionId"]
                else:
                    queries["task_session_id"] = {
                        "$in": list(set(task_session_id_list))
                    }
            else:
                return FailedResponse.NOT_FOUND

        if params["eventId"]:
            return self._load_data_with_event_id(params, queries)

        # 包含未标记、忽略、已确认、误报各类型数据
        if not marker_status:
            return self._load_all_data(params, queries)

        # 仅未标记数据，应用所有忽略策略
        if (
            len(marker_status) == 1
            and marker_status[0] == InspectionEventMarkerStatus.UN_MARKED.value
        ):
            self._set_ignore_strategies(
                params, queries, self.ignore_strategies_handler.all_strategies
            )
            return self._load_unmark_data(params, queries)

        # 仅已标记数据
        if InspectionEventMarkerStatus.UN_MARKED.value not in marker_status:
            return self._load_marked_data(params, queries, marker_status)

        # 如果不包含已忽略状态，则应用忽略策略
        if InspectionEventMarkerStatus.IGNORE.value not in marker_status:
            self._set_ignore_strategies(
                params,
                queries,
                self.ignore_strategies_handler.ignore_strategies,
            )
        # 如果不包含误报状态，则应用误报忽略策略
        elif InspectionEventMarkerStatus.FALSE_POSITIVE not in marker_status:
            self._set_ignore_strategies(
                params,
                queries,
                self.ignore_strategies_handler.false_positive_strategies,
            )
        return self._load_mix_data(params, queries, marker_status)

    def _set_ignore_strategies(self, params, queries, ignore_strategies):
        single_keys = []
        single_urls = []
        # 加载忽略策略
        ignore_strategy_filters = []
        target_url = params["url"]
        plugin_key = params["key"]
        for key in ignore_strategies:
            for url, _id in ignore_strategies[key].items():
                if target_url and url in target_url:
                    continue
                if plugin_key and key in plugin_key:
                    continue
                if not url:
                    single_keys.append(key)
                    continue
                if not key:
                    single_urls.append(url)
                    continue
                _url = url.replace("https://", "").replace("http://", "")
                if _url.endswith("/"):
                    _url = _url[:-1]
                ignore_strategy_filters.append(
                    {"detail.key": key, "detail.url": {"$regex": rf"{_url}"}}
                )
        # 具体忽略策略
        if ignore_strategy_filters:
            queries["$nor"] = ignore_strategy_filters

        if not target_url:
            # 单独key或url的忽略策略集合
            if single_urls:
                queries["target_url"] = {
                    "$not": {"$regex": "|".join(single_urls)}
                }
        else:
            queries["target_url"] = target_url

        if single_keys:
            queries["detail.key"] = {"$not": {"$regex": "|".join(single_keys)}}

    def _load_data_with_event_id(self, params, queries):
        inspected_event = self._get_inspected_event_by_event_id(params["eventId"])
        queries["event_id"] = params["eventId"]
        return self._parse_res(params, queries, inspected_event)

    def _load_marked_data(self, params, queries, marker_status):
        inspected_events = self._load_inspected_events(
            queries["created_at"], marker_status
        )
        queries["event_id"] = {
            "$in": [inspected_event for inspected_event in inspected_events]
        }
        return self._parse_res(params, queries, inspected_events)

    def _load_unmark_data(self, params, queries):
        # 从巡检表加载事件列表，用于过滤已标记的事件
        event_id_list = self._load_event_id_from_inspection(
            queries["created_at"]
        )
        if event_id_list:
            queries["event_id"] = {"$nin": event_id_list}

        return self._parse_res(params, queries, {})

    def _load_all_data(self, params, queries):
        inspected_events = self._load_inspected_events(
            queries["created_at"],
            [
                InspectionEventMarkerStatus.IGNORE.value,
                InspectionEventMarkerStatus.CONFIRM.value,
                InspectionEventMarkerStatus.FALSE_POSITIVE.value,
            ],
        )
        return self._parse_res(params, queries, inspected_events)

    def _load_mix_data(self, params, queries, marker_status):
        marker_status.remove(InspectionEventMarkerStatus.UN_MARKED.value)
        exclude_marker_status = [1, 2, 3]
        for _status in marker_status:
            exclude_marker_status.remove(_status)
        exclude_events = self._load_inspected_events(
            queries["created_at"], exclude_marker_status
        )

        include_events = self._load_inspected_events(
            queries["created_at"], marker_status
        )
        queries["event_id"] = {
            "$nin": [inspected_event for inspected_event in exclude_events]
        }
        return self._parse_res(params, queries, include_events)

    def _parse_res(self, params, queries, inspected_events):
        # 翻页信息
        page = params["page"]
        per_page = params["perPage"]

        query = _Query(
            query_type=QueryType.FIND,
            queries=queries,
            pagination=(page, per_page),
            selected_fields=self._select_fields(),
            order_by="-created_at",
        )
        res = self.execute(query)

        inspection_summary = self.today_inspection_summary()

        if not res.count:
            return {
                "code": Code.OK.value,
                "msg": MSG.NOT_FOUND.value,
                "results": {
                    "inspectionStatistics": inspection_summary,
                },
            }
        event_list = []
        task_session_set = set()
        for event in res.results:
            event_id = event["event_id"]
            task_session_id = event["task_session_id"]
            if event.event_name == "statistics":
                broken_links = event.detail.get("broken_links")
                if not event.detail.get("broken_links"):
                    continue
                event_name = "broken_links"
                for broken_link in broken_links:
                    result = {
                        "eventId": event_id,
                        "url": event["target_url"],
                        "name": event["detail"].get("name", {}).get("zh_cn")
                        if event["event_name"] == "vuln"
                        else SCAN_EVENT_TYPE_NAME_MAPPING.get(
                            event_name, event_name
                        ),
                        "severity": event["severity"],
                        "eventType": event["event_type"],
                        "key": "statistics",
                        "taskSessionId": task_session_id,
                        "engine": event["job_name"],
                        "markerStatus": inspected_events.get(event_id, 0),
                        "foundAt": (
                            event["created_at"] + datetime.timedelta(hours=8)
                        ).isoformat(),
                        "user": "",  # TODO:
                        "detailText": event["detail"].get("extra_detail", ""),  # 风险详情暂时只对detail.extra_detail 生效
                        "riskUrl": broken_link.get("url", "")
                    }
                    event_list.append(result)
            else:
                result = {
                    "eventId": event_id,
                    "url": event["target_url"],
                    "name": event["detail"].get("name", {}).get("zh_cn")
                    if event["event_name"] == "vuln"
                    else SCAN_EVENT_TYPE_NAME_MAPPING.get(
                        event["event_name"], event["event_name"]
                    ),
                    "severity": event["severity"],
                    "eventType": event["event_type"],
                    "key": event["detail"].get("key"),
                    "taskSessionId": task_session_id,
                    "engine": event["job_name"],
                    "markerStatus": inspected_events.get(event_id, 0),
                    "foundAt": (
                        event["created_at"] + datetime.timedelta(hours=8)
                    ).isoformat(),
                    "user": "",  # TODO:
                    "detailText": event["detail"].get("extra_detail", ""),  # 风险详情暂时只对detail.extra_detail 生效
                    "riskUrl": event["detail"].get("affect", "") or event["detail"].get("url", "")
                }
                event_list.append(result)
            task_session_set.add(task_session_id)

        task_session_asset_info_mapping = (
            self._get_asset_info_with_task_session(list(task_session_set))
        )

        if not params["username"]:
            task_session_user_mapping = self.get_user_with_task_sessions(
                list(task_session_set)
            )
        else:
            task_session_user_mapping = {
                task_session_id: params["username"]
                for task_session_id in task_session_set
            }

        for event in event_list:
            username = task_session_user_mapping.get(
                event["taskSessionId"], ""
            )
            source_ip = task_session_asset_info_mapping.get(
                event["taskSessionId"], {}
            ).get("sourceIp", "")
            asset_name = task_session_asset_info_mapping.get(
                event["taskSessionId"], {}
            ).get("note", "")
            if not username:
                continue
            event["user"] = username
            event["sourceIp"] = source_ip
            event["assetName"] = asset_name
        total = self._get_total_count(queries)
        return {
            "code": Code.OK.value,
            "msg": MSG.OK.value,
            "results": {
                "count": len(event_list),
                "total": total,
                "inspectionStatistics": inspection_summary,
                "data": event_list,
                "page": page,
                "perPage": per_page,
            },
        }

    def _get_total_count(self, queries):
        col = ScanEvents._get_collection()
        res = col.find(queries, {}).count()
        # res = ScanEvents.objects.find(queries).itcount()
        return res

    def _select_fields(self) -> List:
        return [
            "event_id",
            "event_name",
            "target_url",
            "detail.broken_links",
            "detail.name.zh_cn",
            "detail.affect",
            "detail.url",
            "detail.extra_detail",
            "detail.key",
            "job_name",
            "severity",
            "event_type",
            "task_session_id",
            "created_at",
        ]

    def _load_event_id_from_inspection(self, found_at):
        query = _Query(
            query_type=QueryType.FIND,
            queries={"foundAt": found_at},
            selected_fields=["eventId"],
        )
        event_res = self.execute(query, col=ScanEventsInspection)
        event_id_list = []
        for event_obj in event_res.results:
            event_id_list.append(event_obj.eventId)
        return event_id_list

    def today_inspection_summary(self) -> Dict:
        pipeline = [
            {
                "$match": {
                    "createdAt": {
                        "$gte": datetime.datetime.fromisoformat(
                            datetime.date.today().isoformat()
                        ),
                        "$lt": datetime.datetime.fromisoformat(
                            (
                                datetime.date.today()
                                + datetime.timedelta(days=1)
                            ).isoformat()
                        ),
                    }
                }
            },
            {
                "$group": {
                    "_id": f"${ScanEventsInspection.markerStatus.name}",
                    "count": {"$sum": 1},
                }
            },
        ]
        query = _Query(
            query_type=QueryType.AGG, queries=pipeline, selected_fields=[]
        )
        summary_res = self.execute(query, col=ScanEventsInspection)
        if not summary_res.count:
            return {}
        summary = {}
        for res in summary_res.results:
            summary[res["_id"]] = res["count"]
        for marker_status in [
            InspectionEventMarkerStatus.IGNORE.value,
            InspectionEventMarkerStatus.CONFIRM.value,
            InspectionEventMarkerStatus.FALSE_POSITIVE.value,
        ]:
            if marker_status not in summary:
                summary[marker_status] = 0
        summary[0] = sum(summary.values())
        return summary

    def event_list(self, form):
        result = self._get_results(**form.data)
        return result

    def _get_task_sessions_with_uid(self, params, query_time) -> List:
        query = _Query(
            query_type=QueryType.FIND,
            queries={"username": params["username"]},
            selected_fields=[User.id.name],
        )
        user_res = self.execute(query, User)
        uid = ""
        if user_res.count > 0:
            user = user_res.results[0]
            uid = user.id
        if not uid:
            return []

        if params["eventType"] == 1:
            task_types = TASK_TYPES[0]
        else:
            task_types = TASK_TYPES[1]

        if isinstance(task_types, list):
            task_type_filter = {"$in": task_types}
        else:
            task_type_filter = task_types
        task_query = _Query(
            query_type=QueryType.FIND,
            queries={
                "startTime": query_time,
                "uid": uid,
                "taskType": task_type_filter,
            },
            selected_fields=[Task.taskSessionId.name],
        )
        task_res = self.execute(task_query, col=Task)
        task_session_id_list = []
        if task_res.count == 0:
            return task_session_id_list
        for task in task_res.results:
            task_session_id_list.append(task.taskSessionId)
        return task_session_id_list

    def _get_inspected_event_by_event_id(self, event_id) -> Dict:
        query = _Query(
            query_type=QueryType.FIND,
            queries={"eventId": event_id},
            selected_fields=["eventId", "markerStatus"],
        )
        return self._get_event_mark_status(query)

    def _load_inspected_events(self, query_time, marker_status) -> Dict:
        query = _Query(
            query_type=QueryType.FIND,
            queries={
                "foundAt": query_time,
                "markerStatus": {"$in": marker_status},
            },
            selected_fields=["eventId", "markerStatus"],
        )
        return self._get_event_mark_status(query)

    def _get_event_mark_status(self, query) -> Dict:
        event_res = self.execute(query, col=ScanEventsInspection)
        if event_res.count == 0:
            return {}
        inspected_events = {}
        for event in event_res.results:
            inspected_events[event.eventId] = event.markerStatus.value
        return inspected_events

    def _get_asset_info_with_task_session(self, task_sessions):
        task_session_jobs_mapping = {}
        if not task_sessions:
            return task_sessions
        pipeline = [
            {
                "$lookup": {
                    "from": "jobs",
                    "localField": "jobId",
                    "foreignField": "_id",
                    "as": "result",
                }
            },
            {"$match": {"taskSessionId": {"$in": task_sessions}}},
        ]
        query = _Query(
            query_type=QueryType.AGG,
            queries=pipeline,
            selected_fields=[
                "taskSessionId",
                "result.sourceIp",
                "result.note",
            ],
        )
        res = self.execute(query, col=Task)
        for r in res.results:
            if not r.get("taskSessionId"):
                continue
            if not r.get("result"):
                continue
            if r["result"][0].get("sourceIp") is None:
                continue
            task_session_jobs_mapping[r["taskSessionId"]] = {
                "sourceIp": r["result"][0]["sourceIp"],
                "note": r["result"][0]["note"],
            }
        return task_session_jobs_mapping


@singleton
class EventDetailHandler(BaseHandler):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.col = ScanEvents

    def _init_query(self, **kwargs) -> _Query:
        event_id = kwargs.get("eventId")
        return _Query(
            query_type=QueryType.FIND,
            queries={"event_id": event_id},
            selected_fields=self._select_fields(),
        )

    def _select_fields(self) -> List:
        return ScanEvents._fields.keys()

    def event_detail(self, form) -> Dict:
        result = self._event_detail(form)
        return result

    def _event_detail(self, form) -> Dict:
        query = self._init_query(**form.data)
        details = self.execute(query)
        if not details.results:
            return FailedResponse.NOT_FOUND
        details = details.results[0]
        created_at = details.created_at
        if isinstance(created_at, datetime.datetime):
            created_at = (created_at + datetime.timedelta(hours=8)).isoformat()
        event_details = {
            "eventId": details.event_id,
            "taskSessionId": details.task_session_id,
            "url": details.target_url,
            # "name": details.event_name,
            "severity": details.severity,
            "markerStatus": self._get_marker_status(
                details.event_id, fields=["markerStatus"]
            ),  # TODO://
            "foundAt": created_at,
            "user": "",  # TODO://
            "key": details.detail.get("key", ""),
            "engine": details.job_name,
            "pluginCode": "",
            "detailText": details.detail.get("extra_detail", ""),  # 风险详情暂时只对detail.extra_detail 生效
            "riskUrl": details.detail.get("affect", "")
        }

        task_session_id_user_map = self.get_user_with_task_sessions(
            [details.task_session_id]
        )
        user = task_session_id_user_map.get(details.task_session_id, "")
        event_details["user"] = user

        asset_info = self._get_task_info(details.task_session_id)
        if asset_info:
            asset_name = asset_info.get("note", "")
            asset_address = asset_info.get("targetUrl", "")
            source_ip = asset_info.get("sourceIp", "")
            event_details["assetName"] = asset_name
            event_details["assetAddress"] = asset_address
            event_details["sourceIp"] = source_ip

            start_time = asset_info.get("startTime", "")
            end_time = asset_info.get("endTime", "")

            if isinstance(end_time, datetime.datetime) and isinstance(
                start_time, datetime.datetime
            ):
                duration = end_time - start_time
                end_time = end_time.isoformat()
                seconds = duration.seconds
                minutes, seconds = divmod(seconds, 60)
                event_details["scanEndTime"] = end_time
                event_details["scanDuration"] = "{}分钟{}秒".format(
                    minutes, seconds
                )

            addition = asset_info.get("addition", {})
            event_details["responseIp"] = addition.get("target_ip", "")
            event_details["scanUrlsCount"] = addition.get(
                "statistics", {}
            ).get("urls", 0)

            event_details["defensiveInfo"] = "暂无"

            task_settings = asset_info.get("taskSettings", {})
            alert_settings = asset_info.get("alertSettings", {})

            enable_site_portrait_trigger_monitor = task_settings.get(
                "enableSitePortraitTriggerMonitor", False
            )

            collect = task_settings.get("collect", {})

            monitor_type = self._get_collect_type(
                trigger_type=asset_info.get("triggerType", ""), collect=collect
            )
            if details.event_name == "vuln":
                monitor_contents = "所有类型漏洞"
            else:
                monitor_contents = "，".join(
                    [
                        TASK_EVENT_TYPE_NAME_MAPPING.get(name)
                        for name in collect.get("plugins", [])
                        if TASK_EVENT_TYPE_NAME_MAPPING.get(name)
                    ]
                )
            monitor_speed_mapping = {"medium": "高、中、低危", "high": "仅高危"}
            monitor_speed = monitor_speed_mapping.get(
                collect.get("speed", "high"), ""
            )

            monitor_rate = task_settings.get("interval", 60000)
            seconds = monitor_rate / 1000
            if seconds < 60:
                seconds = 60
            monitor_rate = self._get_monitor_rate(seconds)
            monitor_depth = collect.get("depth", 3)
            monitor_max_page = collect.get("maxPage", 1000)

            notification = alert_settings.get("notification", {})
            notification_type_name_mapping = {
                "email": "邮件",
                "sms": "短信",
                "wechat": "微信",
                "inBox": "站内信",
            }
            notification_type = "、".join(
                [
                    notification_type_name_mapping.get(_notification)
                    for _notification in notification
                    if notification[_notification]
                ]
            )
            alert_group_ids = alert_settings.get("alertGroupIds")
            alert_group_name = ""
            if alert_group_ids:
                alert_group_id = alert_group_ids[0]
                alert_group_name = self._get_alert_group(
                    {"id": ObjectId(alert_group_id)}
                )
            elif not alert_group_name and asset_info.get("uid"):
                alert_group_name = self._get_alert_group(
                    {"uid": ObjectId(asset_info["uid"]), "isDefault": True}
                )
            else:
                alert_group_name = ""

            scan_settings = {
                "assetName": asset_name,
                "assetAddress": asset_address,
                "monitorType": monitor_type,
                "monitorContent": monitor_contents,
                "monitorRate": monitor_rate,
                "monitorSpeed": monitor_speed,
                "monitorDepth": monitor_depth,
                "monitorMaxPage": monitor_max_page,
                "alertGroup": alert_group_name,
                "alertType": notification_type,
                "enableSitePortraitTriggerMonitor": enable_site_portrait_trigger_monitor,
            }

            event_details["monitorSettings"] = scan_settings

        _details = getattr(self, f"_{details.event_name}_detail_parser")(
            details.detail
        )
        if details.event_name == "vuln":
            if details.job_name in ENGINE_PLUGINS_CODE:
                event_details["pluginCode"] = "{}{}".format(
                    os.path.join(
                        ENGINE_PLUGINS_CODE[details.job_name]["path"],
                        details.detail["key"],
                    ),
                    ENGINE_PLUGINS_CODE[details.job_name]["ext"],
                )
        else:
            event_details["pluginCode"] = ENGINE_PLUGINS_CODE.get(
                details.event_name, {}
            ).get("path", "")
        marked_data = self._get_marked_data(event_id=details.event_id)
        if marked_data:
            supporting_image_ids = marked_data["supportingImageIds"]
            event_details["markerInfo"] = {
                "markerTime": marked_data["createdAt"],
                "remarks": marked_data["remarks"],
                "markerStatus": marked_data["markerStatus"],
            }
            images = []
            if supporting_image_ids:
                for supporting_image_id in supporting_image_ids:
                    image = self._get_image(supporting_image_id)
                    if not image:
                        continue
                    images.append(image)
            if images:
                event_details["markerInfo"]["images"] = images

        event_details.update(_details)
        return {
            "code": Code.OK.value,
            "msg": MSG.OK.value,
            "results": event_details,
        }

    def get_event_details(
        self, event_ids, fields
    ) -> Dict[str, Dict[str, str]]:
        def get_field(result, field) -> str:
            if "detail." in field:
                inner_fields = field.split(".")
                for f in inner_fields:
                    if isinstance(result, dict):
                        result = result.get(f, {})
                    else:
                        result = getattr(result, f, "")
                return result or ""
            else:
                return getattr(result, field, "")

        query = _Query(
            query_type=QueryType.FIND,
            queries={"event_id": {"$in": event_ids}},
            selected_fields=fields,
        )
        res = self.execute(query)
        event_details = {}
        if not res.count:
            return event_details

        for r in res.results:
            event_details[r.event_id] = {
                field: get_field(r, field) for field in fields
            }
        return event_details

    def _get_alert_group(self, filters):
        res = AlertGroup.objects.filter(**filters).only(*["name"]).first()
        if not res:
            return ""
        return res.name

    def _get_image(self, image_id):
        query = _Query(
            query_type=QueryType.FIND, queries={"_id": ObjectId(image_id)}
        )
        res = self.execute(query, col=SupportingImages)
        if not res.count:
            return ""
        res = res.results[0]
        image_data = res.content
        try:
            image_data = image_data.decode()
        except:
            return ""
        return image_data

    def _get_collect_type(self, trigger_type, collect):
        if trigger_type == "schedule":
            return "周期性全量" if collect.get("type") == "full" else "周期性"
        elif trigger_type == "scheduleIncrement":
            return "周期性增量"

        elif trigger_type == "manual":
            return "自定义单次"

        elif trigger_type == "special":
            return "专项"

    def _get_monitor_rate(self, seconds):
        minutes, seconds = divmod(seconds, 60)
        hours, minutes = divmod(minutes, 60)
        days, hours = divmod(hours, 24)
        weeks, days = divmod(days, 7)
        rate = ""
        _rate = {
            "周": int(weeks),
            "天": int(days),
            "小时": int(hours),
            "分钟": int(minutes),
        }
        for __rate, value in _rate.items():
            if not value:
                continue
            rate += f"{value}{__rate}"
        rate += "1次"
        return rate

    def _vuln_detail_parser(self, detail) -> Dict:
        vuln_name = detail["name"].get("zh_cn")
        if not vuln_name or vuln_name == "无":
            vuln_name = detail["name"].get("en_us")

        vuln_id = ""

        vuln_ids = (
            detail.get("cwe")
            or detail.get("cnnvd")
            or detail.get("cnvd")
            or detail.get("cve")
            or detail.get("cncve")
            or detail.get("cncnnvd")
            or detail.get("ssv")
        )
        if vuln_ids:
            vuln_id = ", ".join(vuln_ids)

        event_details = {
            "name": detail["name"],
            "eventName": vuln_name or "vuln",
            "vulnType": detail["vuln_type"],
            "vulnId": vuln_id,
            "payload": detail["payload"],
            "description": detail["description"],
            "impact": detail["impact"],
            "recommendation": detail["recommendation"],
            "reference": detail["reference"],
        }
        forensics = []
        for traffic in detail["traffics"]:
            self._decode_content(traffic, "request")
            self._decode_content(traffic, "response")
            forensics.append(traffic)
        event_details["forensics"] = forensics
        return event_details

    def _risk_link_detail_parser(self, detail) -> Dict:
        for result in detail["results"]:
            if not result:
                continue
            risk_type = result["risk_type"]
            result["risk_type"] = RISK_TYPE.get(int(risk_type), {}).get(
                "en_us", ""
            )
            result["risk_type_name"] = RISK_TYPE.get(int(risk_type), {}).get(
                "zh_cn", ""
            )
        event_details = {
            "forensics": detail["results"],
            "eventName": SCAN_EVENT_TYPE_NAME_MAPPING["risk_link"],
            "riskAddress": detail["url"],
        }
        return event_details

    def _black_links_detail_parser(self, detail) -> Dict:
        _detail = {
            "links": detail["links"],
            "refer_path": detail["refer_path"],
            "keywords": detail["keywords"],
        }
        event_details = {
            "forensics": [_detail],
            "eventName": SCAN_EVENT_TYPE_NAME_MAPPING["black_links"],
            "riskAddress": detail["url"],
        }
        return event_details

    def _cryptojacking_detail_parser(self, detail) -> Dict:
        event_details = {
            "forensics": [detail],
            "eventName": SCAN_EVENT_TYPE_NAME_MAPPING["cryptojacking"],
            "riskAddress": detail["url"],
        }
        return event_details

    def _broken_links_detail_parser(self, detail) -> Dict:
        event_details = {
            "forensics": [{"links": detail}],
            "eventName": SCAN_EVENT_TYPE_NAME_MAPPING["broken_links"],
            "riskAddress": detail["url"],
        }
        return event_details

    def _malscan_detail_parser(self, detail) -> Dict:
        event_details = {"forensics": [detail], "eventName": "malscan"}
        return event_details

    def _get_task_info(self, task_session_id):
        pipeline = [
            {
                "$lookup": {
                    "from": "jobs",
                    "localField": "jobId",
                    "foreignField": "_id",
                    "as": "asset_info",
                }
            },
            {"$match": {"taskSessionId": task_session_id}},
        ]
        query = _Query(
            query_type=QueryType.AGG,
            queries=pipeline,
            selected_fields=[
                "asset_info.note",
                "asset_info.targetUrl",
                "asset_info.alertSettings",
                "asset_info.sourceIp",
                "startTime",
                "endTime",
                "result.addition",
                "taskSettings",
                "alertSettings",
                "triggerType",
                "uid",
            ],
        )
        res = self.execute(query, col=Task)
        if not res.count:
            return {}
        res = [r for r in res.results]
        if not res:
            return {}
        res = res[0]
        if not res.get("result"):
            return {}
        results = {
            "endTime": res["endTime"] + datetime.timedelta(hours=8),
            "startTime": res["startTime"] + datetime.timedelta(hours=8),
            "addition": res.get("result", {}).get("addition", {}),
            "taskSettings": res.get("taskSettings", {}),
            "triggerType": res.get("triggerType", ""),
            "uid": res.get("uid", ""),
        }
        if (
            res.get("asset_info")
            and res["asset_info"]
            and isinstance(res["asset_info"][0], dict)
        ):
            results["note"] = res["asset_info"][0].get("note", "")
            results["targetUrl"] = res["asset_info"][0].get("targetUrl", "")
            if res["asset_info"][0].get("alertSettings", {}):
                results["alertSettings"] = res["asset_info"][0].get(
                    "alertSettings", {}
                )
            results["sourceIp"] = res["asset_info"][0].get("sourceIp", "")
        return results

    def _get_marker_status(self, event_id, fields):
        query = _Query(
            query_type=QueryType.FIND,
            queries={"eventId": event_id},
            selected_fields=fields,
        )
        res = self.execute(query, col=ScanEventsInspection)
        if not res.count:
            return 0
        res = res.results[0]
        return res.markerStatus.value

    def _get_marked_data(self, event_id):
        query = _Query(
            query_type=QueryType.FIND, queries={"eventId": event_id}
        )
        res = self.execute(query, col=ScanEventsInspection)
        if not res.count:
            return {}
        res = res.results[0]
        return {
            "createdAt": (
                res.createdAt + datetime.timedelta(hours=8)
            ).isoformat(),
            "markerStatus": res.markerStatus.value,
            "remarks": res.remarks,
            "supportingImageIds": res.supportingImageIds,
        }

    def _decode_content(self, traffic, field):
        request_content = traffic[field].pop("b64content")
        if request_content:
            request_content = self._b64decode(request_content)
        traffic[field]["content"] = request_content

    def _b64decode(self, b64content):
        try:
            content = base64.b64decode(b64content.encode()).decode()
        except Exception:
            content = b64content
        return content


class MarkHandler(BaseHandler):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.col = ScanEventsInspection

    def _init_query(self, **kwargs) -> Iterator[_Query]:
        event_ids = kwargs["eventIds"]
        marker_status = kwargs["markerStatus"]
        remarks = kwargs.get("remarks")
        supporting_images_id = kwargs.get("supportingImageIds")
        event_details = kwargs.get("eventDetails")
        now = utc_now()
        for event_id in event_ids:
            yield _Query(
                query_type=QueryType.UPDATE,
                queries={
                    "filters": {"eventId": event_id},
                    "upsert": True,
                    "update": {
                        "eventId": event_id,
                        "markerStatus": marker_status,
                        "remarks": remarks,
                        "supportingImageIds": supporting_images_id,
                        "taskSessionId": event_details.get(event_id, {}).get(
                            "task_session_id", ""
                        ),
                        "foundAt": event_details.get(event_id, {}).get(
                            "created_at"
                        ),
                        "createdAt": now,
                        "updatedAt": now,
                    },
                },
            )

    def mark(self, form):
        image_id_list = []
        form_data = form.data
        marker_status = form.markerStatus.data

        if marker_status == InspectionEventMarkerStatus.RESET.value:
            event_ids = self._check_event_id_exists(form_data["eventIds"])
            if not event_ids:
                _result = {
                    "code": Code.OK.value,
                    "msg": MSG.OK.value,
                    "results": {},
                }
            else:
                form_data["eventIds"] = event_ids
                _result = self._mark_as_reset(form_data)
            log_info = {
                "logSource": LOG_SOURCE,
                "affects": [],
                "actionMessage": "标记事件",
                "detail": f'标记事件({event_ids})为重置，标记结果为: {json.dumps(_result["results"])}',
            }
            USecurityInspectionLog(**log_info).info(
                log_info["actionMessage"], log_info["detail"]
            )
            return _result
        if form_data["supportingImages"]:
            image_id_list = self._save_images(
                form_data["eventIds"][0], form_data["supportingImages"]
            )
        form_data["supportingImageIds"] = image_id_list
        event_details = EventDetailHandler().get_event_details(
            form_data["eventIds"],
            fields=[
                "event_id",
                "task_session_id",
                "created_at",
                "target_url",
                "detail.key",
                "event_name",
                "detail.url",
                "detail.results",
                "detail.name.zh_cn",
            ],
        )
        form_data["eventDetails"] = event_details
        if marker_status == InspectionEventMarkerStatus.IGNORE.value:
            _result = self._mark_as_ignore(form_data)
        elif marker_status == InspectionEventMarkerStatus.CONFIRM.value:
            _result = self._mark_as_confirm(form_data)
        elif marker_status == InspectionEventMarkerStatus.FALSE_POSITIVE.value:
            _result = self._mark_as_false_positive(form_data)
        else:
            _result = {
                "code": Code.OK.value,
                "msg": MSG.NOT_FOUND.value,
                "results": {},
            }
        event_ids = form_data["eventIds"]
        _ignore_strategies = []
        for event_id in event_ids:
            url = (
                form_data["eventDetails"]
                .get(event_id, {})
                .get("detail.url", "")
            )
            key = (
                form_data["eventDetails"]
                .get(event_id, {})
                .get("detail.key", "")
            )
            _ignore_strategies.append(
                {
                    "key": key,
                    "url": url,
                }
            )
        log_info = {
            "logSource": LOG_SOURCE,
            "affects": [],
            "actionMessage": "标记事件",
            "detail": f'标记事件({form_data["eventIds"]})为{MARKER_NAME_MAPPING[marker_status]}，标记结果为: {json.dumps(_result["results"])}，生成忽略策略为: {json.dumps(_ignore_strategies)}',
        }
        USecurityInspectionLog(**log_info).info(
            log_info["actionMessage"], log_info["detail"]
        )
        return _result

    def _check_event_id_exists(self, event_ids) -> List:
        query = _Query(
            query_type=QueryType.FIND,
            queries={"eventId": {"$in": event_ids}},
            selected_fields=["eventId"],
        )
        event_id_list = []
        res = self.execute(query)
        if not res.count:
            return event_id_list
        for result in res.results:
            event_id_list.append(result.eventId)
        return event_id_list

    def _mark_as_ignore(self, form_data):
        results = []
        for query in self._init_query(**form_data):
            res = self.execute(query)
            event_id = query.queries["update"]["eventId"]
            url = (
                form_data["eventDetails"]
                .get(event_id, {})
                .get("detail.url", "")
            )
            key = (
                form_data["eventDetails"]
                .get(event_id, {})
                .get("detail.key", "")
            )
            event_name = (
                form_data["eventDetails"]
                .get(event_id, {})
                .get("event_name", "")
            )
            if event_name == "risk_link":
                icp_mark = self._resolve_icp_mark(event_id, form_data, query)
                if icp_mark:
                    results.append(icp_mark)
                    continue
            self._add_strategies(
                key,
                url,
                form_data["remarks"],
                InspectionStrategyIgnoreType.IGNORED.value,
            )
            results.append({event_id: res.count})
        if results:
            return {
                "code": Code.OK.value,
                "msg": MSG.OK.value,
                "results": {"count": len(results), "data": results},
            }
        else:
            return {
                "code": Code.OK.value,
                "msg": MSG.NOT_AVAILABLE.value,
                "results": {},
            }

    def _mark_as_confirm(self, form_data):
        results = []
        for query in self._init_query(**form_data):
            res = self.execute(query)
            event_id = query.queries["update"]["eventId"]
            results.append({event_id: res.count})
        return {
            "code": Code.OK.value,
            "msg": MSG.OK.value,
            "results": {"count": len(results), "data": results},
        }

    def _mark_as_reset(self, form_data):
        event_ids = form_data["eventIds"]
        marked_data = self._get_marked_data(event_ids)
        results = []
        for event_id in event_ids:
            data = marked_data.get(event_id)
            if not data:
                results.append({event_id: 0})
                continue
            if (
                data["markerStatus"]
                == InspectionEventMarkerStatus.IGNORE.value
            ):
                self._reset_ignore_marker(event_id)
            elif (
                data["markerStatus"]
                == InspectionEventMarkerStatus.CONFIRM.value
            ):
                pass
            elif (
                data["markerStatus"]
                == InspectionEventMarkerStatus.FALSE_POSITIVE.value
            ):
                self._reset_false_positive_marker(event_id, data)
            res = self._del_record(event_id)
            results.append({event_id: res})
        if results:
            return {
                "code": Code.OK.value,
                "msg": MSG.OK.value,
                "results": {"count": len(results), "data": results},
            }
        else:
            return {
                "code": Code.OK.value,
                "msg": MSG.NOT_AVAILABLE.value,
                "results": {},
            }

    def _mark_as_false_positive(self, form_data):
        results = []
        for query in self._init_query(**form_data):
            event_id = query.queries["update"]["eventId"]
            url = (
                form_data["eventDetails"]
                .get(event_id, {})
                .get("detail.url", "")
            )

            event_name = (
                form_data["eventDetails"]
                .get(event_id, {})
                .get("event_name", "")
            )
            event_name = FRONT_ENGINE_KEY_MAPPING.get(event_name, event_name)

            vuln_name = (
                form_data["eventDetails"]
                .get(event_id, {})
                .get("detail.name.zh_cn", "")
            )

            if event_name == "risk_link":
                icp_mark = self._resolve_icp_mark(event_id, form_data, query)
                if icp_mark:
                    results.append(icp_mark)
                    continue

            key = (
                form_data["eventDetails"]
                .get(event_id, {})
                .get("detail.key", "")
            )
            task_session_id = (
                form_data["eventDetails"]
                .get(event_id, {})
                .get("task_session_id", "")
            )
            if event_name == "vuln":
                name = "vul"
            else:
                name = "securityEvent"
                key = event_name
            key = FRONT_ENGINE_KEY_MAPPING.get(key, key)
            task = self.get_task_result_warnings(task_session_id)
            task_result = task.result
            task_id = task.taskId
            task_type = task.taskType
            if not task_result:
                res = self.execute(query)
                results.append({event_id: res.count})
                continue
            warning = None
            for _warning in task_result.warnings:
                if _warning.affects == url and (_warning.title == vuln_name or _warning.category == event_name):
                    query.queries["update"]["warningId"] = _warning.id
                    query.queries["update"][
                        "warningDetail"
                    ] = _warning.to_dict()
                    warning = _warning
                    break

            res = self.execute(query)
            if res.count and warning:
                task_result.warnings.remove(warning)
                task_result.warningCount -= 1
                task_result.warningUnresolvedCount -= 1
            if self.update_task_warnings(task_session_id, task_result):
                self._refresh_asset_cache(task_type, task_id)
            self._add_strategies(
                key=key,
                url=url,
                remarks="",
                ignore_type=InspectionStrategyIgnoreType.FALSE_POSITIVE.value,
            )
            results.append({event_id: res.count})
        if results:
            return {
                "code": Code.OK.value,
                "msg": MSG.OK.value,
                "results": {"count": len(results), "data": results},
            }
        else:
            return {
                "code": Code.OK.value,
                "msg": MSG.NOT_AVAILABLE.value,
                "results": {},
            }

    def _resolve_icp_mark(self, event_id, form_data, query):
        _results = (
            form_data["eventDetails"]
            .get(event_id, {})
            .get("detail.results", [])
        )
        target_url = (
            form_data["eventDetails"].get(event_id, {}).get("target_url", "")
        )
        if len(_results) == 1 and int(_results[0].get("risk_type", 0)) == 1:
            risk_url = _results[0].get("risk_url", "")
            if urlparse(risk_url).hostname == urlparse(target_url).hostname:
                res = self.execute(query)
                return {event_id: res.count}
        return None

    def _refresh_asset_cache(self, task_type, task_id):
        asset_task = AssetTasks.objects.filter(
            **{f"{task_type}Settings__taskId": task_id}
        ).first()
        asset_task._get_web_security_last_result(is_save=True)

    def _get_marked_data(self, event_ids):
        query = _Query(
            query_type=QueryType.FIND,
            queries={"eventId": {"$in": event_ids}},
            selected_fields=[
                "eventId",
                "warningId",
                "taskSessionId",
                "markerStatus",
                "supportingImageIds",
                "warningDetail",
            ],
        )
        marked_data = {}
        result = self.execute(query, col=ScanEventsInspection)
        if not result.count:
            return marked_data
        for res in result.results:
            marked_data[res.eventId] = {
                "warningId": res.warningId,
                "taskSessionId": res.taskSessionId,
                "markerStatus": res.markerStatus.value,
                "supportingImageIds": res.supportingImageIds,
                "warningDetail": res.warningDetail,
            }
        return marked_data

    def _reset_ignore_marker(self, event_id):
        query = _Query(
            query_type=QueryType.FIND,
            queries={"event_id": event_id},
            selected_fields=["detail", "target_url"],
        )
        res = self.execute(query, col=ScanEvents)
        if res.count:
            result = res.results[0]
            url = result.detail.get("url", "")
            key = result.detail.get("key", "")
            target_url = result.target_url
            if (
                key == "risk_link"
                and len(result.detail.get("results", [])) == 1
            ):
                risk_url = result.detail.get("results")[0].get("risk_url", "")
                if (
                    urlparse(risk_url).hostname
                    == urlparse(target_url).hostname
                ):
                    return True
        else:
            return 0
        if url or key:
            ignore_strategy_delete_query = _Query(
                query_type=QueryType.DELETE,
                queries={"filters": {"url": url, "key": key}},
            )
            res = self.execute(
                ignore_strategy_delete_query, col=IgnoreStrategies
            )
            return res.count
        else:
            return 0

    def _reset_false_positive_marker(self, event_id, data):
        res = self._reset_ignore_marker(event_id)
        if res is True:
            return 1
        task = self.get_task_result_warnings(data["taskSessionId"])
        task_result = task.result
        task_id = task.taskId
        task_type = task.taskType
        if not task_result:
            return 0
        warning_ids = {
            warning_data.id: warning_data.foundAt
            for warning_data in task_result.warnings
        }
        if data["warningId"] not in warning_ids or (
            data["warningId"] in warning_ids
            and data["warningDetail"].foundAt != warning_ids[data["warningId"]]
        ):
            task_result.warnings.append(data["warningDetail"])
            task_result.warningCount += 1
            task_result.warningUnresolvedCount += 1
        else:
            return 1
        res = self.update_task_warnings(data["taskSessionId"], task_result)
        if res:
            self._refresh_asset_cache(task_type, task_id)
        if res:
            return 1
        return 0

    def _del_record(self, event_id):
        query = _Query(
            query_type=QueryType.DELETE,
            queries={"filters": {self.col.eventId.name: event_id}},
        )
        res = self.execute(query)
        return res.count

    def get_task_result_warnings(self, task_session_id) -> Optional[Task]:
        query = _Query(
            query_type=QueryType.FIND,
            queries={"taskSessionId": task_session_id},
            selected_fields=["result", "taskId", "taskType"],
        )
        res = self.execute(query, col=Task)
        if not res.count:
            return None
        res = res.results[0]
        return res

    def update_task_warnings(self, task_session_id, result):
        # query = _Query(
        #     query_type=QueryType.UPDATE,
        #     queries={
        #         "filters": {"taskSessionId": task_session_id},
        #         "update": {"result": result},
        #     },
        # )
        # res = Task.objects.filter(**{"taskSessionId": task_session_id}).update(result=result)
        # res = self.execute(query, col=Task)
        col = Task._get_collection()
        res = col.update(
            {"taskSessionId": task_session_id},
            {"$set": {"result": result.to_dict()}},
        )
        if res["nModified"] == 1:
            return True
        else:
            return False

    def _add_strategies(self, key, url, remarks, ignore_type):
        res = IgnoreStrategiesHandler().add_ignore_strategy(
            {
                "key": key,
                "url": url,
                "remarks": remarks,
                "ignoreType": ignore_type,
            }
        )
        return res

    def _save_images(self, event_id, supporting_images):
        images_insert = []
        image_id_list = []
        for supporting_image in supporting_images:
            image_binary = supporting_image.encode()
            image_unique = hashlib.md5(image_binary).hexdigest()
            find_res = self.execute(
                query=_Query(
                    query_type=QueryType.FIND,
                    queries={"unique": image_unique},
                    selected_fields=["unique"],
                ),
                col=SupportingImages,
            )
            if find_res.count:
                image_id = str(find_res.results[0].id)
                image_id_list.append(image_id)
                continue
            image_binary = Binary(image_binary)
            now = utc_now()
            images_insert.append(
                {
                    "eventId": event_id,
                    "unique": image_unique,
                    "content": image_binary,
                    "createAt": now,
                    "updateAt": now,
                }
            )
        if image_id_list and len(image_id_list) == len(supporting_images):
            return image_id_list
        save_res = self.execute(
            query=_Query(query_type=QueryType.CREATE, queries=images_insert),
            col=SupportingImages,
        )

        if not save_res.count:
            return image_id_list
        for result in save_res.results:
            image_id_list.append(str(result.id))
        return image_id_list


@singleton
class IgnoreStrategiesHandler(BaseHandler):
    def __init__(self, **kwargs):
        self.ignore_strategies_handler = _IgnoreStrategiesHandler()
        super().__init__(**kwargs)
        self.col = IgnoreStrategies

    def _select_fields(self) -> List:
        return IgnoreStrategies._fields.keys()

    def _init_query(self, **kwargs) -> _Query:
        page = kwargs.pop("page", 1)
        per_page = kwargs.pop("perPage", 10)
        params = {}
        for key, value in kwargs.items():
            if value is None:
                continue
            if value == "":
                params[key] = value
            else:
                params[key] = {"$regex": value}

        query = _Query(
            query_type=QueryType.FIND,
            queries=params,
            selected_fields=self._select_fields(),
            order_by="-createdAt",
        )
        if page and per_page:
            query.pagination = (page, per_page)
        return query

    def _init_add_query(self, **kwargs) -> _Query:
        url = kwargs.pop("url", "")
        key = kwargs.pop("key", "")
        if key == "foreign_links":
            key = "risk_link"
        if key == "cryjack":
            key = "cryptojacking"
        ignore_type = kwargs.get(
            "ignoreType", InspectionStrategyIgnoreType.IGNORED.value
        )
        filters = {"url": url, "key": key}
        now = utc_now()
        update = {
            "url": url,
            "key": key,
            "ignoreType": ignore_type,
            "switchStatus": InspectionStrategySwitchStatus.OPEN.value,
            "createdAt": now,
            "updatedAt": now,
        }
        if kwargs.get("remarks"):
            update["remarks"] = kwargs["remarks"]

        return _Query(
            query_type=QueryType.UPDATE,
            queries={"filters": filters, "upsert": True, "update": update},
        )

    def _modify_query(self, **kwargs) -> _Query:
        strategy_id = kwargs.pop("strategyId")
        update = {}
        for key, value in kwargs.items():
            if value is None:
                continue
            update[key] = value
        filters = {"id": ObjectId(strategy_id)}
        update["updatedAt"] = utc_now()
        return _Query(
            query_type=QueryType.UPDATE,
            queries={"filters": filters, "update": update},
        )

    def _delete_query(self, **kwargs) -> _Query:
        strategy_id = kwargs.pop("strategyId")
        filters = {"id": ObjectId(strategy_id)}
        return _Query(
            query_type=QueryType.DELETE, queries={"filters": filters}
        )

    def _select_query(self, strategy_id):
        return _Query(
            query_type=QueryType.FIND, queries={"_id": ObjectId(strategy_id)}
        )

    def list(self, form) -> Dict:
        query = self._init_query(**form.data)
        strategies_res = self.execute(query)
        strategies_list = []
        for _strategies in strategies_res.results:
            created_at = _strategies.createdAt
            if isinstance(created_at, datetime.datetime):
                created_at = (
                    created_at + datetime.timedelta(hours=8)
                ).isoformat()

            key = _strategies.key
            if key == "risk_link":
                key = "foreign_links"
            if key == "cryptojacking":
                key = "cryjack"

            strategies = {
                "strategyId": str(_strategies.id),
                "url": _strategies.url,
                "key": key,
                "remarks": _strategies.remarks,
                "switchStatus": _strategies.switchStatus.value
                if isinstance(
                    _strategies.switchStatus, InspectionStrategySwitchStatus
                )
                else 0,
                "createdAt": created_at,
            }
            strategies_list.append(strategies)
        total = self._get_total_count(query.queries)
        if strategies_list:
            return {
                "code": Code.OK.value,
                "msg": MSG.OK.value,
                "results": {
                    "count": len(strategies_list),
                    "total": total,
                    "data": strategies_list,
                    "page": query.pagination[0],
                    "perPage": query.pagination[1],
                },
            }
        return {
            "code": Code.OK.value,
            "msg": MSG.OK.value,
            "results": {},
        }

    def _get_total_count(self, queries):
        res = IgnoreStrategies.objects.find(queries).count()
        return res

    def add(self, form):
        if not form.url.data and not form.key.data:
            _result = {
                "code": Code.OK.value,
                "msg": MSG.OK.value,
                "results": {},
            }
        else:
            _result = self.add_ignore_strategy(form.data)
        log_info = {
            "logSource": LOG_SOURCE,
            "affects": [],
            "actionMessage": "新增忽略策略",
            "detail": f"新增忽略策略: {json.dumps({k: v for k, v in form.data.items() if v})}",
        }
        CSecurityInspectionLog(**log_info).info(
            log_info["actionMessage"], log_info["detail"]
        )
        return _result

    def add_ignore_strategy(self, data):
        query = self._init_add_query(**data)
        result = self.execute(query)
        if result.count:
            return {
                "code": Code.OK.value,
                "msg": MSG.OK.value,
                "results": {"count": result.count},
            }

    def modify(self, form):
        query = self._modify_query(**form.data)
        modify_res = self.execute(query)
        if modify_res.count:
            _result = {
                "code": Code.OK.value,
                "msg": MSG.OK.value,
                "results": {"count": modify_res.count},
            }
        else:
            _result = {
                "code": Code.OK.value,
                "msg": MSG.OK.value,
                "results": {},
            }
        detail = f"忽略策略id: {form.strategyId.data}"
        for key, value in form.data.items():
            if value is None:
                continue
            if key == "strategyId":
                continue
            detail += f", {key}: {value}"

        log_info = {
            "logSource": LOG_SOURCE,
            "affects": [],
            "actionMessage": "更新忽略策略",
            "detail": detail
        }
        USecurityInspectionLog(**log_info).info(
            log_info["actionMessage"], log_info["detail"]
        )
        return _result

    def delete(self, form):
        select_query = self._select_query(form.strategyId.data)
        select_res = self.execute(select_query)
        detail = f"删除忽略策略，策略id: {form.strategyId.data}"
        if not select_res.count:
            _result = {
                "code": Code.OK.value,
                "msg": MSG.OK.value,
                "results": {},
            }
        else:
            result = select_res.results[0]
            for key in result:
                if not result[key]:
                    continue
                detail += f", {key}: {result[key]}"
            query = self._delete_query(**form.data)
            delete_res = self.execute(query)
            if delete_res.count:
                _result = {
                    "code": Code.OK.value,
                    "msg": MSG.OK.value,
                    "results": {"count": delete_res.count},
                }
            else:
                _result = {
                    "code": Code.OK.value,
                    "msg": MSG.OK.value,
                    "results": {},
                }
        if _result["results"]:
            detail += ", 删除成功"
        else:
            detail += ", 删除失败"
        log_info = {
            "logSource": LOG_SOURCE,
            "affects": [],
            "actionMessage": "删除忽略策略",
            "detail": detail
        }
        DSecurityInspectionLog(**log_info).info(
            log_info["actionMessage"], log_info["detail"]
        )
        return _result
